package dao

import (
	"asoul-fan/museum-api/internal/entity"
	"github.com/lib/pq"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"time"
)

const (
	tableName = "video"
)

type (
	Video interface {
		Insert(data *entity.Video) error
		Update(data *entity.Video) error
		Delete(id int64) error
		FindOneById(id int64) (*entity.Video, error)
		FindAllByTags(tags []int64, nextKey, limit uint) ([]*entity.Video, error)
	}

	videoPostgresSlice []*videoPostgres

	videoPostgres struct {
		Id          int64  `json:"id"`
		SourceType  uint8  `json:"source_type"`
		Title       string `json:"title"`
		TargetUrl   string `json:"target_url"`
		PublishTime int64  `json:"publish_time"`
		CreatedAt   int64  `json:"created_at"`
		DeletedAt   int64  `json:"deleted_at"`
		//CoverUUID   uuid.UUID     `json:"-" gorm:"column:cover_uuid"`
		CoverUrl string        `json:"cover_url"`
		Tags     pq.Int64Array `json:"tags" gorm:"column:tags;type:integer[]"`
	}

	videoPostgresImpl struct {
		conn *gorm.DB
	}
)

func NewVideoPostgresImpl(conn *gorm.DB) Video {
	return &videoPostgresImpl{conn: conn}
}

func newVideoPostgres(data *entity.Video) *videoPostgres {
	_data := &videoPostgres{
		Id:          data.Id,
		SourceType:  data.SourceType,
		Title:       data.Title,
		TargetUrl:   data.TargetUrl,
		PublishTime: data.PublishTime,
		CreatedAt:   data.CreatedAt,
		DeletedAt:   data.DeletedAt,
		//CoverUUID:   data.Cover.UUID,
		CoverUrl: data.CoverUrl,
		Tags:     pq.Int64Array{},
	}

	if data.Tags != nil {
		for _, tag := range data.Tags {
			_data.Tags = append(_data.Tags, tag.Id)
		}
	}

	return _data
}

func (v *videoPostgres) toVideo() *entity.Video {
	_data := &entity.Video{
		Id:         v.Id,
		SourceType: v.SourceType,
		Title:      v.Title,
		//Cover:       entity.Media{UUID: v.CoverUUID},
		CoverUrl:    v.CoverUrl,
		TargetUrl:   v.TargetUrl,
		PublishTime: v.PublishTime,
		CreatedAt:   v.CreatedAt,
		DeletedAt:   v.CreatedAt,
		Tags:        nil,
	}

	if v.Tags != nil {
		for _, tag := range v.Tags {
			_data.Tags = append(_data.Tags, entity.Tag{Id: tag})
		}
	}

	return _data
}

func (list videoPostgresSlice) toVideoSlice() []*entity.Video {
	_list := make([]*entity.Video, 0, len(list))
	for _, postgres := range list {
		_list = append(_list, postgres.toVideo())
	}

	return _list
}

func (v *videoPostgresImpl) Update(data *entity.Video) error {
	_data := newVideoPostgres(data)
	result := v.conn.Table(tableName).Where("id = ?", _data.Id).Updates(&_data)

	if result.Error != nil {
		return errors.Wrap(result.Error, "update video error")
	}

	data = _data.toVideo()

	return nil
}

func (v *videoPostgresImpl) Insert(data *entity.Video) error {
	_data := newVideoPostgres(data)
	result := v.conn.Table(tableName).Omit("Cover").Create(_data)

	if result.Error != nil {
		return errors.Wrap(result.Error, "insert video error")
	}

	data = _data.toVideo()

	return nil
}

func (v *videoPostgresImpl) Delete(id int64) error {
	result := v.conn.Table(tableName).Where("id = ? AND deleted_at = 0", id).Updates(map[string]interface{}{"deleted_at": time.Now().Unix()})

	if result.Error != nil {
		return errors.Wrap(result.Error, "update video error")
	}

	return nil
}

func (v *videoPostgresImpl) FindOneById(id int64) (*entity.Video, error) {
	var data *videoPostgres
	result := v.conn.Table(tableName).Where("id = ? AND deleted_at = 0", id).Find(&data)

	if result.Error != nil {
		return nil, errors.Wrap(result.Error, "select video error")
	}

	if result.RowsAffected == 0 {
		return nil, nil
	}

	return data.toVideo(), nil
}

func (v *videoPostgresImpl) FindAllByTags(tags []int64, nextKey, limit uint) ([]*entity.Video, error) {
	var list videoPostgresSlice
	result := v.conn.Table(tableName).Where("id < ? AND tags && ?", nextKey, pq.Int64Array(tags)).
		Order("id DESC").Limit(int(limit)).
		Find(&list)

	if result.Error != nil {
		return nil, errors.Wrap(result.Error, "select video error")
	}

	return list.toVideoSlice(), nil
}
